package cn.skyquant.quant4j.service.boot.web.interceptor;

import cn.skyquant.quant4j.api.account.AccountDTO;
import cn.skyquant.quant4j.api.account.AccountService;
import cn.skyquant.quant4j.api.authority.AuthorityService;
import cn.skyquant.quant4j.api.authority.UserDTO;
import cn.skyquant.quant4j.api.dto.ResultDTO;
import cn.skyquant.quant4j.api.enums.ApiAccessType;
import cn.skyquant.quant4j.api.enums.BorkerType;
import cn.skyquant.quant4j.api.service.BaseServiceProxy;
import cn.skyquant.quant4j.service.boot.model.authority.AppContext;
import cn.skyquant.quant4j.service.boot.web.WebConstants;
import cn.skyquant.quant4j.service.boot.web.annotation.NoVerify;
import cn.skyquant.quant4j.sdk.enums.Environment;
import cn.skyquant.quant4j.sdk.util.digest.AES;
import cn.skyquant.quant4j.sdk.util.log.LogUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * Created by harley-dog on 2016/11/24.
 */
@Component
public class SecurityInterceptor implements HandlerInterceptor {

    @Value("${runtime.environment}")
    Environment environment;

    @Value("${server.domain}")
    String domain;

    @Value("${server.cookie.path}")
    String cookie_path;

    @Value("${server.cookie.max-age}")
    int cookie_max_age;

    @Autowired
    AuthorityService authorityService;

    @Autowired
    AccountService accountService;

    @Autowired
    AppContext appContext;

    private ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
        threadLocal.set(System.currentTimeMillis());
        String accessType = request.getHeader(ApiAccessType.class.getSimpleName());
        //如果是
        if (ApiAccessType.APP.name().equals(accessType)) {
            return verifyApp(request, response, o);
        } else {
            return verifyWeb(request, response, o);
        }
    }

    private static final ResultDTO err1 = ResultDTO.error("没有登录不能访问");
    private static final ResultDTO err2 = ResultDTO.error("登录信息验证无效");
    private static final ResultDTO err3 = ResultDTO.error("非法链接");
    private static final ResultDTO err4 = ResultDTO.error("非法请求");
    private static final ResultDTO err5 = ResultDTO.error("非法令牌");

    private boolean verifyApp(HttpServletRequest request, HttpServletResponse response, Object o) {
        if (o instanceof HandlerMethod) {
            String uri = request.getRequestURI();
            String remoteAddr = request.getRemoteAddr();
            String method = request.getMethod();
            String token = request.getHeader(BaseServiceProxy.TOKEN_KEY);
            String referer = request.getHeader(BaseServiceProxy.REFERER_key);
            String accountCode = request.getHeader(BaseServiceProxy.CODE_KEY);
            String borkerTypeStr = request.getHeader(BaseServiceProxy.BORKER_KEY);
            if (StringUtils.isEmpty(token)
                    || StringUtils.isEmpty(accountCode)
                    || StringUtils.isEmpty(borkerTypeStr)
                    || StringUtils.isEmpty(referer)
                    || !referer.equals(BaseServiceProxy.REFERER)) {
                printResult(response, err4);
                return false;
            }
            LogUtil.info(LogUtil.web, String.format("%s access %s from app, uri=%s,token=%s", remoteAddr, method, uri, token));
            String decryptstr = null;
            try {
                decryptstr = AES.decrypt(token);
            } catch (Exception e) {
                printResult(response, err5);
                return false;
            }
            if (decryptstr == null) {
                printResult(response, err5);
                return false;
            }

            try {
                long time = Long.parseLong(decryptstr);     //客户端传过来的时间
                long currentTime = System.currentTimeMillis();  //服务端传过来的时间
                //服务器时间和客户端时间不能超过10分钟
                if (Math.abs(currentTime - time) > 6000000) {
                    return false;
                }
                HandlerMethod handlerMethod = (HandlerMethod) o;
                BorkerType borkerType = BorkerType.valueOf(borkerTypeStr);
                ResultDTO<AccountDTO> accountDTOResultDTO = accountService.get(accountCode, borkerType);
                if (accountDTOResultDTO.okData()) {
                    appContext.set(accountDTOResultDTO.data);//往线程变量里设置登录信息
                    return true;
                }
                RestController restController = handlerMethod.getBeanType().getAnnotation(RestController.class);
                //如果token和tel验证失败了，再判断是不是noverfity，否则就没法拿到cookie了
                NoVerify noVerifyBeanType = handlerMethod.getBeanType().getAnnotation(NoVerify.class);
                NoVerify noVerifyMethod = handlerMethod.getMethodAnnotation(NoVerify.class);
                if (noVerifyBeanType != null || noVerifyMethod != null) {
                    return true;
                }
            } catch (NumberFormatException e) {
                printResult(response, err5);
                return false;
            }
        }
        return false;
    }

    private boolean verifyWeb(HttpServletRequest request, HttpServletResponse response, Object o) {
        //referer校验，在开发模式下不需要校验
        String referer = request.getHeader("referer");
        String uri = request.getRequestURI();
        String remoteAddr = request.getRemoteAddr();
        String method = request.getMethod();
        LogUtil.info(LogUtil.web, String.format("%s access %s from web, uri=%s,referer=%s", remoteAddr, method, uri, referer));
        if (Environment.product.equals(environment) && (referer == null || !referer.contains(domain))) {
            printResult(response, err3);
            return false;
        }
        if (o instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) o;
            RestController restController = handlerMethod.getBeanType().getAnnotation(RestController.class);
            //如果token和tel验证失败了，再判断是不是noverfity，否则就没法拿到cookie了
            NoVerify noVerifyBeanType = handlerMethod.getBeanType().getAnnotation(NoVerify.class);
            NoVerify noVerifyMethod = handlerMethod.getMethodAnnotation(NoVerify.class);
            if (restController != null) {

                //否则进行cookie的校验
                Cookie[] cookies = request.getCookies();
                //没有cookie直接不通过
                if (cookies == null || cookies.length == 0) {//只要类上或者方法上加了noVerify注解，则不需要验证cookie直接通过
                    if (noVerifyBeanType != null || noVerifyMethod != null) {
                        return true;
                    }
                    printResult(response, err1);
                    return false;

                }
                Cookie cookieTel = null, cookieToken = null, cookieLastGmtLogin = null;
                for (Cookie cookie : cookies) {
                    if (cookie == null || cookie_path.equals(cookie.getPath())) {
                        continue;
                    }
                    if (WebConstants.COOKIE_TEL.equals(cookie.getName())) {
                        cookieTel = cookie;
                    } else if (WebConstants.COOKIE_TOKEN.equals(cookie.getName())) {
                        cookieToken = cookie;
                    } else if (WebConstants.COOKIE_LOGIN_TIME.equals(cookie.getName())) {
                        cookieLastGmtLogin = cookie;
                    }
                }
                //cookie里没有tel或者token，直接不通过
                if (cookieTel == null || cookieToken == null || cookieLastGmtLogin == null
                        || StringUtils.isEmpty(cookieTel.getValue()) || StringUtils.isEmpty(cookieToken.getValue())) {

                    //只要类上或者方法上加了noVerify注解，则不需要验证cookie直接通过
                    if (noVerifyBeanType != null || noVerifyMethod != null) {
                        return true;
                    }
                    printResult(response, err1);
                    return false;
                }
                String tel = cookieTel.getValue();
                String token = cookieToken.getValue();
                ResultDTO<UserDTO> userResult = authorityService.verifyToken(tel, token);
                //校验token和tel失败，直接不通过
                if (!userResult.okData()) {
                    //只要类上或者方法上加了noVerify注解，则不需要验证cookie直接通过
                    if (noVerifyBeanType != null || noVerifyMethod != null) {
                        return true;
                    }
                    printResult(response, err2);
                    return false;
                }
                appContext.set(userResult.data);//往线程变量里设置登录信息
                return true;
            }
        }
        return false;
    }

    public void printResult(HttpServletResponse response, ResultDTO resultDTO) {
        response.setContentType("application/json; charset=utf-8");
        PrintWriter writer = null;
        try {
            writer = response.getWriter();
            writer.print(JSONObject.toJSONString(resultDTO, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat));
            writer.close();
            response.flushBuffer();
        } catch (IOException e) {
            LogUtil.error(String.format("print [5s] to response error", resultDTO.toString()), e);
        }
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        Long begin = threadLocal.get();
        if (begin != null) {
            long diff = System.currentTimeMillis() - begin;
            LogUtil.info(LogUtil.web, String.format("%s, time=%dms", request.getRequestURI(), diff));
        }
        //每次记得清理线程变量
        threadLocal.remove();
        appContext.remove();
    }
}
